完整的散步流程只剩下以下兩點還沒完成:
今天要先完成第一部分。
散步過程中,畫面上的距離、時間、速度,都是 App 根據目前收到的 GPS 點位即時計算出來的。
但使用者按下結束後,真正保存到資料庫的數據,並不會直接使用畫面上的結果,而是重新由後端計算一次。

真正需要保存到資料庫的正式統計,我選擇全部交給後端計算,原因有幾個:
因此這裡的分工會是:前端負責顯示散步進行中的即時數據,後端則負責產生最後保存下來的正式統計。
使用者按下結束後,會先停止接收新的 GPS 點位,再將本地尚未同步的資料送到後端。
流程如下:
按下結束散步
↓
停止定位
↓
同步剩餘 GPS 點位
↓
後端計算正式統計
↓
更新散步狀態
↓
顯示完成頁
正式統計前,後端會先取得這次散步經過過濾後的有效 GPS 點位,距離與速度等數據都只會使用這些有效點位計算。
總距離不能只拿第一個點和最後一個點來計算。
假設使用者繞著公園走了一圈,最後又回到起點:
A → B → C → D → A
這時起點和終點的位置可能非常接近,如果只計算兩者之間的距離,最後得到的結果甚至可能接近 0 公尺,但使用者實際上已經走完一整圈。
因此,總距離要將每一組相鄰 GPS 點位之間的距離累加起來:
GPS1
│ 120 m
▼
GPS2
│ 150 m
▼
GPS3
│ 100 m
▼
GPS4
這樣從 GPS1 到 GPS4 的總距離就是:
120 + 150 + 100 = 370 m
程式碼如下:
let totalDistance = 0;
for (let index = 1; index < validPoints.length; index += 1) {
const previousPoint = validPoints[index - 1];
const currentPoint = validPoints[index];
totalDistance += calculateDistance(
previousPoint,
currentPoint,
);
}
兩個經緯度之間的距離可以使用 Haversine 公式計算,前幾天已經介紹過,這裡就不再重複。
由於正式統計使用的是經過過濾後的有效點位,因此前面已經排除掉的 GPS 漂移、瞬間跳點等異常資料,也不會被計入最後的距離。
散步的總時間可以直接使用結束時間減去開始時間。
但總時間不等於實際移動時間,例如:
走路 20 分鐘 → 休息 10 分鐘 → 再走 20 分鐘
這次散步的結果會是:
因此,平均移動速度應該使用移動時間計算,而不是總時間。
平均移動速度 = 距離(km)÷ 移動時間(h)
假設使用者在 40 分鐘內走了 2 公里,那麼平均移動速度的計算方式為:
40 分鐘 = 40 ÷ 60 = 0.667 小時
2 ÷ 0.667 ≈ 3 km/h
如果把休息時間也一起算進去,得到的平均速度就會比使用者實際走路時更低,因此這裡需要將暫停時間排除。
除了平均移動速度之外,我也會計算這次散步中的最快速度。
GPS 點位是每隔一段時間記錄一次,因此這裡會利用相鄰兩個有效點位之間的距離與時間,算出每一小段的移動速度:
每段速度 = 相鄰點位之間的距離 ÷ 經過時間
例如:
GPS A
時間:10:00:00
GPS B
時間:10:00:05
兩點距離:8 公尺
代表使用者在這 5 秒內移動了 8 公尺,因此這一段的速度就是:
8 ÷ 5 = 1.6 m/s
1.6 × 3.6 = 5.76 km/h
其中 3.6 是將公尺/秒轉換成公里/小時的換算倍率:
const maxSpeedKilometersPerHour =
maxSpeedMetersPerSecond * 3.6;
完整程式碼如下:
let maxSpeedMetersPerSecond = 0;
for (let index = 1; index < validPoints.length; index += 1) {
const previousPoint = validPoints[index - 1];
const currentPoint = validPoints[index];
const durationSeconds =
(currentPoint.recordedAt.getTime() -
previousPoint.recordedAt.getTime()) /
1000;
if (durationSeconds <= 0) {
continue;
}
const distanceMeters = calculateDistance(
previousPoint,
currentPoint,
);
const segmentSpeed =
distanceMeters / durationSeconds;
maxSpeedMetersPerSecond = Math.max(
maxSpeedMetersPerSecond,
segmentSpeed,
);
}
const maxSpeedKilometersPerHour =
maxSpeedMetersPerSecond * 3.6;
這段程式會依序計算每兩個有效 GPS 點位之間的速度,再取出其中最大的數值,作為這次散步的最快速度。
最後,這次散步會產生以下幾項正式統計:
後端計算完成後,再將這些結果寫回散步紀錄。
這樣一來不管是完成頁、歷史紀錄,還是之後要做的統計與成就功能,都可以直接使用同一份正式數據。

正式統計完成後,就只剩下將這次走過的路線畫成縮圖。
明天就來分享如何使用 SVG 繪製散步路線縮圖。